# Мьютекс (от англ. "mutual exclusion"), сокращение от "взаимное исключение" — это механизм синхронизации,
# который используется для предотвращения одновременного доступа к общему ресурсу. Он представляет собой объект,
# который блокирует доступ к критической секции кода, пока другая задача не освободит этот ресурс.
# В этом степе мы кратко поговорим о блокировке мьютексом, подробнее об этом мы будем говорить далее в курсе.

# Принцип работы мьютекса:
# Мьютекс может находиться в двух состояниях: заблокированном (locked) и разблокированном (unlocked). При попытке задачи получить
# доступ к общему ресурсу она должна сначала "захватить" мьютекс. Если мьютекс свободен, задаче предоставляется доступ к ресурсу,
# и мьютекс переходит в состояние "заблокирован". Если другая задача пытается получить доступ к ресурсу, пока мьютекс заблокирован,
# эта задача не сможет получить такой доступ и будет ждать освобождения мьютекса.

# Когда задача завершает работу с ресурсом, она должна "освободить" мьютекс, что позволит другим задачам получить доступ к ресурсу.

# Некоторые ситуации, в которых может потребоваться использовать мьютекс:
# Общие структуры данных: Если несколько задач читают и изменяют общую структуру данных (например, список, словарь или пользовательский объект),
# мьютекс может быть использован для обеспечения согласованности данных и предотвращения состояния гонки.
# Доступ к файлам: Если несколько задач записывают данные в один и тот же файл, мьютекс может быть использован для синхронизации
# доступа к файлу и обеспечения корректной записи данных.
# Общие ресурсы системы: Если задачи используют общие ресурсы системы, такие как сетевые соединения или устройства, мьютекс может использоваться
# для синхронизации доступа и предотвращения конфликтов.
# Ограничение одновременного выполнения: В некоторых случаях требуется ограничить число задач, которые могут выполнять определенную операцию одновременно.
# Мьютекс может использоваться для этой цели, ограничивая число корутин, которые имеют доступ к ресурсам или выполняют задачи.
# Кэширование: Мьютекс также может использоваться для синхронизации доступа к кэшированным данным между задачами и предотвращения ситуации, когда несколько
# задач пытаются одновременно обновить кэш.

# В библиотеке asyncio нет классического мьютекса, но для синхронизации доступа к ресурсам можно использовать объекты типа Lock. Они функционируют 
# аналогично мьютексам и предоставляют методы для получения и освобождения блокировки.
# import asyncio

# lock = asyncio.Lock()

# Основные методы для работы с объектом блокировки – это acquire() и release(). Метод acquire() позволяет получить блокировку, а метод release() – 
# освободить ее. Метод acquire()является асинхронным и должен быть вызван с использованием ключевого слова await, в то время как release() вызывается
# напрямую без await.
# await lock.acquire()
# # Критическая секция кода, реализующая любую логику
# lock.release()

# Метод acquire() блокирует доступ к критической секции кода до тех пор, пока мьютекс не будет освобожден исполняемой в нём задачей.  Другими 
# словами, пока задача, исполняемая внутри критического блока кода, не завершит свою работу, никакие другие задачи не смогут получить доступ к
# этому блоку кода. Критическая секция кода должна быть ограничена вызовами методов acquire() и release().

# Важно отметить, что только один человек может находиться в комнате и работать с общим ресурсом одновременно, это гарантирует безопасность и согласованность 
# данных. Таким образом, замок на двери (мьютекс) обеспечивает взаимное исключение доступа к комнате (общему ресурсу).

#Код на эту аналогию.
# import asyncio

# # Эмуляция комнаты с замком
# class Room:
#     def __init__(self):
#         self.lock = asyncio.Lock()

#     async def use(self, name):
#         # Захват мьютекса
#         await self.lock.acquire()
#         try:
#             print(f"{name} вошел в комнату.")
#             # Имитация выполнения работы внутри комнаты
#             await asyncio.sleep(1)
#             print(f"{name} вышел из комнаты.")
#         finally:
#             # Освобождение мьютекса
#             self.lock.release()

# async def person(name, room):
#     # Человек (задача) пытается использовать комнату
#     print(f"{name} хочет войти в комнату.")
#     await room.use(name)

# async def main():
#     room = Room()  # Инициализация комнаты с замком

#     # Создание задач для нескольких людей, пытающихся войти в комнату
#     await asyncio.gather(
#         person("Алексей", room),
#         person("Мария", room),
#         person("Иван", room)
#     )

# asyncio.run(main())

# Room() представляет собой комнату с замком. Метод use() символизирует процесс использования комнаты, который защищен asyncio.Lock().
# Это гарантирует, что в любой момент времени комнатой может пользоваться только один "человек".
# Функция person() представляет человека (или задачу), который хочет использовать комнату. Она вызывает room.use(), ожидая своей очереди, если комната занята.
# В функции main() асинхронно запускаются задачи для нескольких "людей", которые хотят войти в комнату. Использование asyncio.gather()
# позволяет запустить их одновременно, демонстрируя конкуренцию за ресурс (комнату).

#Мьютексы могут быть использованы с помощью менеджера контекста with:

# async with lock:
    # Критическая секция кода реализующая любую логику

# Этот способ использования мьютекса автоматически освобождает мьютекс при выходе из контекста.

# При работе с мьютексами необходимо следить за возможными блокировками задач, так как их использование может привести к дедлоку (deadlock) или 
# зацикливанию программы. Для избегания блокировок можно использовать другие методы синхронизации, такие как семафоры или условные переменные, о
# которых мы тоже будем говорить далее.
# Пример кода с использованием async with lock:

# Исправим код выше и добавим в него менеджер контекста with

import asyncio

class Room:
    def __init__(self):
        self.lock = asyncio.Lock()

    async def use(self, name):
        # Использование менеджера контекста для работы с замком
        async with self.lock:
            print(f"{name} вошел в комнату.")
            # Имитация выполнения работы внутри комнаты
            await asyncio.sleep(1)
            print(f"{name} вышел из комнаты.")

async def person(name, room):
    # Человек (задача) пытается использовать комнату
    print(f"{name} хочет войти в комнату.")
    await room.use(name)

async def main():
    room = Room()  # Инициализация комнаты с замком

    # Создание задач для нескольких людей, пытающихся войти в комнату
    await asyncio.gather(
        person("Алексей", room),
        person("Мария", room),
        person("Иван", room)
    )

asyncio.run(main())

# В этом исправленном коде async with self.lock: заменяет прямое использование await self.lock.acquire() и self.lock.release(),
# обеспечивая автоматическое управление блокировкой. Это делает код более безопасным и упрощает его понимание, поскольку не требуется
# явно вызывать методы захвата и освобождения замка, минимизируя риск ошибок управления состоянием замка.